﻿using FxSuperCore.Log;
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using FxSuperCore.Utils;
using FxWinSuper.UI.Menu.Pages;
using FxWinSupper.Config;

namespace FxWinSuper.Functions
{
	internal class InternalLog : ILogPrint
	{
		private static readonly InternalLog _instance;

		private readonly SimpleLogFormatter _logFormatter = new SimpleLogFormatter();

		private static readonly int MaxLogLineNum = 2000;

		private static readonly int LogLineMaxLength = 4096;

		private static readonly int LogFlushTimeMs = 15 * 60 * 1000;

		private static readonly long LogFileChangeMaxSize = 10 * 1024 * 1024;

		private static readonly string LogFileName = "FxWinSupper.log";

		// 原始日志行，最多存储2000条
		private LinkedList<KeyValuePair<LogLevel, string>> SourceLogLine = new LinkedList<KeyValuePair<LogLevel, string>>();

		// 日志刷盘时的信号量
		private SemaphoreSlim logFlushSemaphoreSlim = new SemaphoreSlim(1);

		// 日志刷盘定时器
		private Timer _logFlushTimer;

		public static InternalLog Instance => _instance;

		public event Action<LogLevel, string> OnPrintLog;

		public event Action OnFlushLog;

		static InternalLog()
		{
			_instance = new InternalLog();
		}

		private InternalLog()
		{
			_logFlushTimer = new Timer(new TimerCallback(FlushLog), null, LogFlushTimeMs, LogFlushTimeMs);
			Application.Current.Exit += (s, e) => FlushLog(null);
		}

		public void ShowLogDirectory()
		{
			string logDir = Path.Combine(Environment.CurrentDirectory, "log");
			Process.Start("explorer.exe", logDir);
		}

		internal LinkedList<KeyValuePair<LogLevel, string>> GetCurrentLogs()
		{
			LinkedList<KeyValuePair<LogLevel, string>> logs = new LinkedList<KeyValuePair<LogLevel, string>>();
			lock (SourceLogLine)
			{
				foreach (KeyValuePair<LogLevel, string> keyValuePair in SourceLogLine)
				{
					logs.AddLast(keyValuePair);
				}
			}

			return logs;
		}

		private void ClearExpireLogFile()
		{
			LogFileExistDay logFileExistDay = Conf.LogFileExistTimeType;
			long expireMill = Convert.ToInt64(new DataTable().Compute(logFileExistDay.GetEnumDesc().Split(':')[1], "")) * 1000;
			string logDir = Path.Combine(Environment.CurrentDirectory, "log");
			if (!Directory.Exists(logDir))
			{
				return;
			}

			long curTimespan = CommonUtils.DatetimeToTimespan(DateTime.UtcNow);
			foreach (FileInfo fileInfo in new DirectoryInfo(logDir).GetFiles())
			{
				long lastWriteTime = CommonUtils.DatetimeToTimespan(fileInfo.LastWriteTimeUtc);
				if (curTimespan - lastWriteTime > expireMill)
				{
					fileInfo.Delete();
				}
			}
		}

		private string GetLogFilePath()
		{
			string sourcefilepath = Path.Combine(Environment.CurrentDirectory, "log", LogFileName);
			string filepath = sourcefilepath;
			int idx = 1;
			if (!File.Exists(filepath) || new FileInfo(filepath).Length < LogFileChangeMaxSize)
			{
				return filepath;
			}

			while (File.Exists(filepath))
			{
				filepath = sourcefilepath + $".{idx++}.log";
			}

			try
			{
				File.Move(sourcefilepath, filepath);
			}
			catch (Exception e)
			{
				Error($"Move Log File Error: {e.Message}");
				throw e;
			}
			return sourcefilepath;
		}

		/// <summary>
		/// 日志刷盘
		/// </summary>
		private void FlushLog(object state)
		{
			logFlushSemaphoreSlim.Wait();
			ClearExpireLogFile();
			if (SourceLogLine.Count <= 0)
			{
				logFlushSemaphoreSlim.Release();
				return;
			}
			StringBuilder sb = new StringBuilder();
			lock (SourceLogLine)
			{
				if (SourceLogLine.Count <= 0)
				{
					logFlushSemaphoreSlim.Release();
					return;
				}
				foreach (KeyValuePair<LogLevel, string> logPair in SourceLogLine)
				{
					sb.AppendLine(logPair.Value);
				}
				SourceLogLine.Clear();
			}

			File.AppendAllText(GetLogFilePath(), sb.ToString());
			logFlushSemaphoreSlim.Release();
			OnFlushLog?.Invoke();
		}

		public string FormatObject(object msg) => _logFormatter.FormatObject(msg);

		private void PrintLog(LogLevel level, object msg)
		{
			if ((int)level < (int)Conf.OutputLogLevel)
			{
				return;
			}
			string log = $"[{DateTime.Now.ToLocalTime()}][{level.GetEnumDesc()}]{FormatObject(msg)}";
			if (log.Length > LogLineMaxLength)
			{
				log = log.Substring(0, LogLineMaxLength) + "...";
			}
			lock (SourceLogLine)
			{
				if (SourceLogLine.Count >= MaxLogLineNum)
				{
					Task.Run(() => FlushLog(null));
				}
				SourceLogLine.AddLast(new KeyValuePair<LogLevel, string>(level, log));
			}
			OnPrintLog?.Invoke(level, log);
		}

		public void Info(object msg) => PrintLog(LogLevel.INFO, msg);

		public void Debug(object msg) => PrintLog(LogLevel.DEBUG, msg);

		public void Warnning(object msg) => PrintLog(LogLevel.WARNNING, msg);

		public void Error(object msg) => PrintLog(LogLevel.ERROR, msg);

		public void Trace(object msg)
		{
			StackTrace stackTrace = new StackTrace();
			StackFrame[] stackFrames = stackTrace.GetFrames();
			PrintLog(LogLevel.TRAE, msg);
			int deep = 0;
			if (stackFrames != null)
			{
				foreach (StackFrame stackFrame in stackFrames)
				{
					deep += 1;
					if (deep == 1)
					{
						continue;
					}
					MethodBase method = stackFrame.GetMethod();
					string log = $"{method.DeclaringType.FullName}.{method.Name}";
					if (deep >= 5)
					{
						log += " ...";
						PrintLog(LogLevel.TRAE, $"{log}...");
						break;
					}

					PrintLog(LogLevel.TRAE, log);
				}
			}
		}
	}
}
